package br.ufrj.dcc.tp.ns;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.TreeSet;

import br.ufrj.dcc.tp.ns.types.LinkEventType;
import br.ufrj.dcc.tp.ns.types.NsTracingElement;
import br.ufrj.dcc.tp.ns.types.NsTracingEvent;
import br.ufrj.dcc.tp.ns.types.NsTracingPacket;
import br.ufrj.dcc.tp.ns.types.PacketEventType;
import br.ufrj.dcc.tp.ns.types.PacketType;


public class NsTracingSimulationProcessor {
	
	private NsTracingFile file;
	private HashMap<Integer, NsTracingPacket> dataPackets, controlPackets;
	private TreeSet<NsTracingEvent> events;
	private NsTracingPacket firstPacketOldRoute, firstPacketNewRoute;
	
	public NsTracingSimulationProcessor(NsTracingFile file){
		this.file = file;
		dataPackets = new HashMap<Integer, NsTracingPacket>();
		controlPackets = new HashMap<Integer, NsTracingPacket>();
		// TreeSet não aceita duplicações.
		// Como evento de DOWN/UP é gerado para as duas combinações
		// de source e destination da enlace, então ok.
		events = new TreeSet<NsTracingEvent>();
	}
	
	public void evaluateMetric1() {
		// Limitar um pouco o número de linhas tratadas para poder entender.
		try{
			BufferedReader in = new BufferedReader(new FileReader(file.getPath()));
			String str;
			NsTracingElement packet = null;
			NsTracingPacket cast = null, last = null;
			
			while (in.ready()) {
				str = in.readLine();
				packet = processaLinha(str);
				
				if(packet == null) {
					continue;
				}
				
				// Se for um trace de pacote
				if(NsTracingPacket.class == packet.getElementType()) {
					cast = (NsTracingPacket) packet;
					PacketType type = cast.getType();
					HashMap<Integer, NsTracingPacket> common = type == PacketType.CBR ? dataPackets : controlPackets;
					last = common.get(cast.getId());
					if(last != null) {
						last.addNsPacket(cast);
					} else {
						common.put(cast.getId(), cast);
					}
				} else if(NsTracingEvent.class == packet.getElementType()){ // Caso contrário é um tracing de evento de enlace
					events.add((NsTracingEvent)packet);
				}
			}
			in.close();
		}catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void evaluateMetric2(NsTracingEvent down) {
		if(down.getType() != LinkEventType.DOWN) {
			throw new IllegalArgumentException();
		}
		
		try{
			BufferedReader in = new BufferedReader(new FileReader(file.getPath()));
			String str;
			NsTracingElement packet = null;
			NsTracingPacket cast = null;
			
			while (in.ready()) {
				str = in.readLine();
				packet = processaLinha(str);
				
				if(packet == null) {
					continue;
				}
				
				if(packet.getElementType() != NsTracingPacket.class) {
					continue;
				}
				
				cast = (NsTracingPacket) packet;
				if(cast.getType() == PacketType.CBR && cast.getTime().compareTo(down.getTime()) == 1 && cast.getEvent() != PacketEventType.DROP) {
					firstPacketNewRoute = cast;
					break;
				}
			}
			in.close();
		}catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void evaluateMetric3(NsTracingEvent up) {
		if(up.getType() != LinkEventType.UP) {
			throw new IllegalArgumentException();
		}
		try{
			BufferedReader in = new BufferedReader(new FileReader(file.getPath()));
			String str;
			NsTracingElement packet = null;
			NsTracingPacket cast = null;
			
			while (in.ready()) {
				str = in.readLine();
				packet = processaLinha(str);
				
				if(packet == null) {
					continue;
				}
				
				if(packet.getElementType() != NsTracingPacket.class) {
					continue;
				}
				
				cast = (NsTracingPacket) packet;
				
				if(cast.getType() == PacketType.CBR && cast.getTime().compareTo(up.getTime()) == 1 && cast.getEvent() != PacketEventType.DROP) {
					firstPacketOldRoute = cast;
					break;
				}
			}
			in.close();
		}catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public NsTracingPacket getFirstPacketOldRoute() {
		return firstPacketOldRoute;
	}
	
	public NsTracingPacket getFirstPacketNewRoute() {
		return firstPacketNewRoute;
	}
	
	public int getDataPacketsLength() {
		return dataPackets.size();
	}
	
	public int getControlPacketsLength() {
		return controlPackets.size();
	}
	
	public NsTracingEvent getDownEvent() {
		for(NsTracingEvent e : events) {
			if(e.getType() == LinkEventType.DOWN) {
				return e;
			}
		}
		return null;
	}
	
	public NsTracingEvent getUpEvent() {
		for(NsTracingEvent e : events) {
			if(e.getType() == LinkEventType.UP) {
				return e;
			}
		}
		return null;
	}
	
	public void clear() {
		dataPackets.clear();
		controlPackets.clear();
		events.clear();
	}
	/*
		Linha: 
		"%c -t "TIME_FORMAT" -s %d -d %d -p %s -e %d -c %d
		-i %d -a %d -x {%s.%s %s.%s %d %s %s}",
	
	Correspondências
	%c tt,
	-t Scheduler::instance().clock(),
	-s s,
	-d d,
	-p name,
	-e th-\>size(),
	-c iph-\>flowid(),
	-i th-\>uid(),
	-a iph-\>flowid(),
	-x {src\_nodeaddr.src\_portaddr
		dst\_nodeaddr.dst\_portaddr
		seqno flags sname}
	*/
	
	private NsTracingElement processaLinha(String str) {
		StringTokenizer tokenizer = new StringTokenizer(str);
		
		int tokenCount = tokenizer.countTokens();
		// Hard-coded: as linhas que interessam tem 23 ou 9 tokens.
		try {
			if(tokenCount != 23 && tokenCount != 9) {
//				System.out.println(str + " NAO TRATADA");
				return null;
			}
			
			if(tokenCount == 23) {
				return buildTracingPacket(tokenizer);
			}
			
			return buildTracingEvent(tokenizer);
		} catch(Exception e) {
//			e.printStackTrace();
//			System.out.println(str + " NAO TRATADA");
		}
		return null;
	}
	
	private NsTracingEvent buildTracingEvent(StringTokenizer tokenizer) {
		String token = null, linkEventType = null;
		int source = 0, destination = 0;
		BigDecimal time = null;
		
		tokenizer.nextToken();
		
		token = tokenizer.nextToken();
		if(token.equals("-t")) {
			token = tokenizer.nextToken();
			// -t * não é um evento válido.
			if(token.equals("*")) {
				return null;
			}
			time = new BigDecimal(token);
		}
		
		token = tokenizer.nextToken();
		if(token.equals("-s")) {
			source = Integer.parseInt(tokenizer.nextToken());
		}
		
		token = tokenizer.nextToken();
		if(token.equals("-d")) {
			destination = Integer.parseInt(tokenizer.nextToken());
		}
		
		token = tokenizer.nextToken();
		if(token.equals("-S")) {
			linkEventType = tokenizer.nextToken();
		}
		
		NsTracingEvent event = new NsTracingEvent();
		event	.setType(LinkEventType.valueOf(linkEventType))
				.setTime(time)
				.setSource(source)
				.setDestination(destination);
		
		return event;
	}
	
	private NsTracingPacket buildTracingPacket(StringTokenizer tokenizer) {
		String token = null, packetType = null;
		BigDecimal time = null;
		char event = tokenizer.nextToken().charAt(0);
		int source = 0, destination = 0, uId = 0;
		
		token = tokenizer.nextToken();
		if(token.equals("-t")) {
			time = new BigDecimal(tokenizer.nextToken());
		}
		
		token = tokenizer.nextToken();
		if(token.equals("-s")) {
			source = Integer.parseInt(tokenizer.nextToken());
		}
		
		token = tokenizer.nextToken();
		if(token.equals("-d")) {
			destination = Integer.parseInt(tokenizer.nextToken());
		}
		
		token = tokenizer.nextToken();
		if(token.equals("-p")) {
			packetType = tokenizer.nextToken();
		}
		
		token = tokenizer.nextToken();
		if(token.equals("-e")) { // Size
			tokenizer.nextToken();
		}
		
		token = tokenizer.nextToken();
		if(token.equals("-c")) { //  FlowId1
			tokenizer.nextToken();
		}
		
		token = tokenizer.nextToken();
		if(token.equals("-i")) {
			uId = Integer.parseInt(tokenizer.nextToken());
		}
		
		token = tokenizer.nextToken();
		if(token.equals("-a")) { // FlowId2
			tokenizer.nextToken();
		}
		
		NsTracingPacket packet = new NsTracingPacket();
		packet	.setType(PacketType.get(packetType))
				.setEvent(PacketEventType.get(event))
				.setId(uId)
				.setSource(source)
				.setTime(time)
				.setDestination(destination);
		return packet;
	}
	
	public NsTracingFile getFile() {
		return file;
	}

	public void setFile(NsTracingFile file) {
		this.file = file;
	}
}
